package top.ieei.demo.dhcp;

import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;


public final class DHCPResponseFactory
{
  private DHCPResponseFactory()
  {
    throw new UnsupportedOperationException();
  }

  public static final DHCPPacket makeDHCPOffer(DHCPPacket request, InetAddress offeredAddress, int leaseTime, InetAddress serverIdentifier, String message, DHCPOption[] options)
  {
    if (request == null) {
      throw new NullPointerException("request is null");
    }
    if (!request.isDhcp()) {
      throw new DHCPBadPacketException("request is BOOTP");
    }
    Byte requestMessageType = request.getDHCPMessageType();
    if (requestMessageType == null) {
      throw new DHCPBadPacketException("request has no message type");
    }
    if (requestMessageType.byteValue() != 1) {
      throw new DHCPBadPacketException("request is not DHCPDISCOVER");
    }

    if (offeredAddress == null) {
      throw new IllegalArgumentException("offeredAddress must not be null");
    }
    if (!(offeredAddress instanceof Inet4Address)) {
      throw new IllegalArgumentException("offeredAddress must be IPv4");
    }

    DHCPPacket resp = new DHCPPacket();

    resp.setOp((byte)2);
    resp.setHtype(request.getHtype());
    resp.setHlen(request.getHlen());

    resp.setXid(request.getXid());

    resp.setFlags(request.getFlags());

    resp.setYiaddr(offeredAddress);

    resp.setGiaddrRaw(request.getGiaddrRaw());
    resp.setChaddr(request.getChaddr());

    resp.setDHCPMessageType((byte)2);

    resp.setOptionAsInt((byte)51, leaseTime);
    resp.setOptionAsInetAddress((byte)54, serverIdentifier);
    resp.setOptionAsString((byte)56, message);

    if (options != null) {
      for (DHCPOption opt : options) {
        resp.setOption(opt.applyOption(request));
      }

    }

    resp.setAddrPort(getDefaultSocketAddress(request, (byte)2));

    return resp;
  }

  public static final DHCPPacket makeDHCPAck(DHCPPacket request, InetAddress offeredAddress, int leaseTime, InetAddress serverIdentifier, String message, DHCPOption[] options)
  {
    if (request == null) {
      throw new NullPointerException("request is null");
    }
    if (!request.isDhcp()) {
      throw new DHCPBadPacketException("request is BOOTP");
    }
    Byte requestMessageType = request.getDHCPMessageType();
    if (requestMessageType == null) {
      throw new DHCPBadPacketException("request has no message type");
    }
    if ((requestMessageType.byteValue() != 3) && (requestMessageType.byteValue() != 8))
    {
      throw new DHCPBadPacketException("request is not DHCPREQUEST/DHCPINFORM");
    }

    if (offeredAddress == null) {
      throw new IllegalArgumentException("offeredAddress must not be null");
    }
    if (!(offeredAddress instanceof Inet4Address)) {
      throw new IllegalArgumentException("offeredAddress must be IPv4");
    }

    DHCPPacket resp = new DHCPPacket();

    resp.setOp((byte)2);
    resp.setHtype(request.getHtype());
    resp.setHlen(request.getHlen());

    resp.setXid(request.getXid());

    resp.setFlags(request.getFlags());
    resp.setCiaddrRaw(request.getCiaddrRaw());
    if (requestMessageType.byteValue() != 8) {
      resp.setYiaddr(offeredAddress);
    }

    resp.setGiaddrRaw(request.getGiaddrRaw());
    resp.setChaddr(request.getChaddr());

    resp.setDHCPMessageType((byte)5);

    if (requestMessageType.byteValue() == 3) {
      resp.setOptionAsInt((byte)51, leaseTime);
    }
    resp.setOptionAsInetAddress((byte)54, serverIdentifier);
    resp.setOptionAsString((byte)56, message);

    if (options != null) {
      for (DHCPOption opt : options) {
        resp.setOption(opt.applyOption(request));
      }

    }

    resp.setAddrPort(getDefaultSocketAddress(request, (byte)5));

    return resp;
  }

  public static final DHCPPacket makeDHCPNak(DHCPPacket request, InetAddress serverIdentifier, String message)
  {
    if (request == null) {
      throw new NullPointerException("request is null");
    }
    if (!request.isDhcp()) {
      throw new DHCPBadPacketException("request is BOOTP");
    }
    Byte requestMessageType = request.getDHCPMessageType();
    if (requestMessageType == null) {
      throw new DHCPBadPacketException("request has no message type");
    }
    if (requestMessageType.byteValue() != 3) {
      throw new DHCPBadPacketException("request is not DHCPREQUEST");
    }

    DHCPPacket resp = new DHCPPacket();

    resp.setOp((byte)2);
    resp.setHtype(request.getHtype());
    resp.setHlen(request.getHlen());

    resp.setXid(request.getXid());

    resp.setFlags(request.getFlags());

    resp.setGiaddrRaw(request.getGiaddrRaw());
    resp.setChaddr(request.getChaddr());

    resp.setDHCPMessageType((byte)6);

    resp.setOptionAsInetAddress((byte)54, serverIdentifier);
    resp.setOptionAsString((byte)56, message);

    resp.setAddrPort(getDefaultSocketAddress(request, (byte)6));

    return resp;
  }

  public static InetSocketAddress getDefaultSocketAddress(DHCPPacket request, byte responseType)
  {
    if (request == null) {
      throw new IllegalArgumentException("request is null");
    }

    InetAddress giaddr = request.getGiaddr();
    InetAddress ciaddr = request.getCiaddr();
    InetSocketAddress sockAdr;
    switch (responseType)
    {
    case 2:
    case 5:
      if (Constants.INADDR_ANY.equals(giaddr))
      {
        if (Constants.INADDR_ANY.equals(ciaddr))
          sockAdr = new InetSocketAddress(Constants.INADDR_BROADCAST, 68);
        else
          sockAdr = new InetSocketAddress(ciaddr, 68);
      }
      else {
        sockAdr = new InetSocketAddress(giaddr, 67);
      }
      break;
    case 6:
      if (Constants.INADDR_ANY.equals(giaddr))
        sockAdr = new InetSocketAddress(Constants.INADDR_BROADCAST, 68);
      else {
        sockAdr = new InetSocketAddress(giaddr, 67);
      }
      break;
    case 3:
    case 4:
    default:
      throw new IllegalArgumentException("responseType not valid");
    }
    return sockAdr;
  }
}
